﻿
using System;
using System.Collections.Generic;

namespace EmperialApps.WeatherSpark.Data {

    /// <summary>Scales data values to screen values.</summary>
    public sealed partial class Scale {

        /// <summary>Initializes a new instance of the <see cref="Scale"/> class.</summary>
        public Scale( bool isInverted, double screenSize, IEnumerable<double> data, double dataMargin = 0.0, double floor = double.NegativeInfinity )
            : this( isInverted, screenSize, floor, GetDataRange( data, dataMargin, floor ) ) { }


        /// <summary>Gets the lowest data value in the range of the scale.</summary>
        public double Minimum { get { return this.Range.Item1; } }

        /// <summary>Gets the highest data value in the range of the scale.</summary>
        public double Maximum { get { return this.Range.Item2; } }


        /// <summary>Returns all increment values in the range of the scale.</summary>
        public IEnumerable<double> GetRangeValues( double increment ) {
            return GetRangeValues( this.Minimum, this.Maximum, increment );
        }

        /// <summary>Returns all increment values between start and end.</summary>
        public static IEnumerable<double> GetRangeValues( double start, double end, double increment ) {
            for( double value = start; value <= end; value += increment )
                yield return value;
        }

        /// <summary>Returns the relative value corresponding to the specified data value.</summary>
        public double ToRelative( double value ) {
            double relative = (value - this.Minimum)
                     / (this.Maximum - this.Minimum);
            if( this.IsInverted )
                relative = 1.0 - relative;
            return relative;
        }

        /// <summary>Returns the screen value corresponding to the specified data value.</summary>
        public double ToScreen( double value ) {
            double relative = ToRelative( value );
            double screen = relative * this.ScreenSize;
            return screen;
        }

        /// <summary>Returns the data value corresponding to the specified screen value.</summary>
        public double FromScreen( double screen ) {
            double relative = screen / this.ScreenSize;
            if( this.IsInverted )
                relative = 1.0 - relative;
            double value = this.Minimum + relative * (this.Maximum - this.Minimum);
            return value;
        }

#if DEBUG
        /// <inheritdoc/>
        public override string ToString( ) {
            return string.Format( "[{0}, {1}] / {2}px{3}", this.Minimum, this.Maximum, this.ScreenSize, this.IsInverted ? ", Inverted" : "" );
        }
#endif

        private static Pair<double, double> GetDataRange( IEnumerable<double> data, double dataMargin, double floor ) {
            double minimum = double.PositiveInfinity;
            double maximum = double.NegativeInfinity;
            foreach( double value in data )
                if( !double.IsNaN( value ) ) {
                    minimum = Math.Min( minimum, Math.Floor( value - dataMargin ) );
                    maximum = Math.Max( maximum, Math.Ceiling( value + dataMargin ) );
                }

            minimum = Math.Max( minimum, floor );
            return Pair.Create( minimum, maximum );
        }
    }

}
